﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Management;
using System.Globalization;
using System.Threading;
using System.IO;
using System.Diagnostics;

namespace  CCF20Model.Common
{
    public struct User
    {
        private string userName;
        private bool allowRead;
        private bool allowFullControl;

        public string UserName
        {
            get { return userName; }
            set { userName = value; }
        }

        public bool AllowRead
        {
            get { return allowRead; }
            set { allowRead = value; }
        }

        public bool AllowFullControl
        {
            get { return allowFullControl; }
            set { allowFullControl = value; }
        }
    }
    public struct Share
    {
        public string ShareName { get; set; }
        public List<User> UsersList { get; set; }
    }
    public class Win32ShareWrapper
    {
        #region NTFS File Permission Access Masks

        // Low level ace flags.

        // NTFS Read - Combination of the following permissions
        //ACE Type: ACCESS_ALLOWED
        //ACE Mask: FILE_READ_DATA, FILE_READ_EA, FILE_EXECUTE, FILE_READ_ATTRIBUTES, READ_CONTROL, SYNCHRONIZE 
        private static uint NTFS_READ = 1179817;
        // NTFS Change - Combination of the following permissions
        //ACE Type: ACCESS_ALLOWED
        //ACE Mask: FILE_READ_DATA, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_READ_EA, FILE_WRITE_EA, FILE_EXECUTE,
        //          FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, DELETE, READ_CONTROL, SYNCHRONIZE 
        private static uint NTFS_CHANGE = 1245631;
        // NTFS Full Control - Combination of the following permissions
        //ACE Type: ACCESS_ALLOWED
        //ACE Mask: FILE_READ_DATA, FILE_WRITE_DATA, FILE_APPEND_DATA, FILE_READ_EA, FILE_WRITE_EA, FILE_EXECUTE, FILE_DELETE_CHILD,
        //          FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE 
        private static uint NTFS_FULLCONTROL = 2032127;

        #endregion

        #region Private Helper Functions

        /// <summary>
        /// The method returns ManagementObject object for the shared folder with given name
        /// </summary>
        /// <param name="sharedFolderName">string containing name of shared folder</param>
        /// <returns>Object of type ManagementObject for the shared folder.</returns>
        private static ManagementObject GetSharedFolderObject(string sharedFolderName)
        {
            ManagementObject sharedFolderObject = null;

            //Creating a searcher object to search
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from Win32_Share where Name = '" + sharedFolderName + "'");
            ManagementObjectCollection resultOfSearch = searcher.Get();
            if (resultOfSearch.Count > 0)
            {
                //The search might return a number of objects with same shared name. I assume there is just going to be one
                foreach (ManagementObject sharedFolder in resultOfSearch)
                {
                    sharedFolderObject = sharedFolder;
                    break;
                }
            }
            return sharedFolderObject;
        }

        /// <summary>
        /// The method returns SecuritySetting ManagementObject object for the shared folder with given name
        /// </summary>
        /// <param name="sharedFolderName">string containing name of shared folder</param>
        /// <returns>Object of type ManagementObject for the shared folder.</returns>
        private static ManagementObject GetSharedFolderSecuritySettingObject(string sharedFolderName)
        {
            ManagementObject sharedFolderObject = null;

            //Creating a searcher object to search
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from Win32_LogicalShareSecuritySetting where Name = '" + sharedFolderName + "'");
            ManagementObjectCollection resultOfSearch = searcher.Get();
            if (resultOfSearch.Count > 0)
            {
                //The search might return a number of objects with same shared name. I assume there is just going to be one
                foreach (ManagementObject sharedFolder in resultOfSearch)
                {
                    sharedFolderObject = sharedFolder;
                    break;
                }
            }
            return sharedFolderObject;
        }

        /// <summary>
        /// The method returns ManagementObject object for the user folder with given name
        /// </summary>
        /// <param name="domain">string containing domain name of user </param>
        /// <param name="alias">string containing the user's network name </param>
        /// <returns>Object of type ManagementObject for the user folder.</returns>
        private static ManagementObject GetUserAccountObject(string domain, string alias)
        {
            ManagementObject userAccountObject = null;
            //ManagementObjectSearcher searcher = new ManagementObjectSearcher(string.Format("select * from Win32_Account where Name = '{0}' and Domain='{1}'", alias, domain));
            ManagementObjectSearcher searcher;
            if (alias.Equals("anonymous"))
            {
                searcher = new ManagementObjectSearcher(string.Format("select * from Win32_Account where SID = '{0}' and Domain='{1}'", alias, domain));
            }
            else
            {
                searcher = new ManagementObjectSearcher(string.Format("select * from Win32_Account where Name = '{0}' and Domain='{1}'", alias, domain));
            }
            ManagementObjectCollection resultOfSearch = searcher.Get();
            if (resultOfSearch.Count > 0)
            {
                foreach (ManagementObject userAccount in resultOfSearch)
                {
                    userAccountObject = userAccount;
                    
                    break;
                }
            }
            if (userAccountObject == null)
            {
                LoggerHelper.LogCCF20(string.Format("Can't Find User Object {0}\\{1} in AD", domain, alias));
            }
            else
            {
                LoggerHelper.LogCCF20(string.Format("Find User Object {0}\\{1} in AD", domain, alias));
            }
            return userAccountObject;
        }

        /// <summary>
        /// Returns the Security Identifier Sid of the given user
        /// </summary>
        /// <param name="userAccountObject">The user object who's Sid needs to be returned</param>
        /// <returns></returns>
        private static ManagementObject GetAccountSecurityIdentifier(ManagementBaseObject userAccountObject)
        {
            ManagementObject securityIdentfierObject = new ManagementObject(string.Format("Win32_SID.SID='{0}'", (string)userAccountObject.Properties["SID"].Value));
            securityIdentfierObject.Get();
            return securityIdentfierObject;
        }

        /// <summary>
        /// Create a trustee object for the given user
        /// </summary>
        /// <param name="domain">name of domain</param>
        /// <param name="userName">the network name of the user</param>
        /// <param name="securityIdentifierOfUser">Object containing User's sid</param>
        /// <returns></returns>
        private static ManagementObject CreateTrustee(string domain, string userName, ManagementObject securityIdentifierOfUser)
        {
            ManagementObject trusteeObject = new ManagementClass("Win32_Trustee").CreateInstance();
            trusteeObject.Properties["Domain"].Value = domain;
            trusteeObject.Properties["Name"].Value = userName;
            trusteeObject.Properties["SID"].Value = securityIdentifierOfUser.Properties["BinaryRepresentation"].Value;
            trusteeObject.Properties["SidLength"].Value = securityIdentifierOfUser.Properties["SidLength"].Value;
            trusteeObject.Properties["SIDString"].Value = securityIdentifierOfUser.Properties["SID"].Value;
            return trusteeObject;
        }

        // Splits full user name into domain name and username
        // assumes that username and domain name are split by '\'
        private static void SplitUserName(string fullName, ref string domainName, ref string userName)
        {
            domainName = string.Empty;
            userName = string.Empty;

            // splitting domain name and user name
            if (fullName.Contains("\\") == true)
            {
                string[] info = fullName.Split('\\');
                if (info.Length >= 2)
                {
                    domainName = info[0].Trim();
                    userName = info[1].Trim();
                }
                else
                {
                    userName = fullName;
                }
            }
            else
            {
                userName = fullName;
            }
        }

        /// <summary>
        /// Create an Access Control Entry object for the given user
        /// </summary>
        /// <param name="trustee">The user's trustee object</param>
        /// <param name="deny">boolean to say if user permissions should be assigned or denied</param>
        /// <returns></returns>
        private static ManagementObject CreateAccessControlEntry(ManagementObject trustee, bool deny, bool allowRead, bool allowFullControl)
        {
            ManagementObject aceObject = new ManagementClass("Win32_ACE").CreateInstance();

            uint accessMask = 0x00;
            if (allowFullControl == true)
            {
                accessMask = NTFS_FULLCONTROL;
            }
            else if (allowRead == true)
            {
                accessMask = NTFS_READ;
            }

            // aceObject.Properties["AccessMask"].Value = 0x1U | 0x2U | 0x4U | 0x8U | 0x10U | 0x20U | 0x40U | 0x80U | 0x100U | 0x10000U | 0x20000U | 0x40000U | 0x80000U | 0x100000U; // all permissions
            aceObject.Properties["AccessMask"].Value = accessMask;
            aceObject.Properties["AceFlags"].Value = 0x0U; // no flags
            aceObject.Properties["AceType"].Value = deny ? 1U : 0U; // 0 = allow, 1 = deny
            aceObject.Properties["Trustee"].Value = trustee;
            return aceObject;
        }        

        #endregion
        
        /// <summary>
        /// Creates a directory share and sets permissions for the given users
        /// Returns the unc path of the share in shareUNCPath
        /// Also returns the warnings generated
        /// </summary>
        /// <param name="sharePath"></param>
        /// <param name="shareName"></param>
        /// <param name="shareDescription"></param>
        /// <param name="users"></param>
        /// <returns></returns>
        public static bool CreateDirectoryShare(string sharePath, string shareName, string shareDescription, User[] users, ref string shareUNCPath, ref string[] warnings)
        {
            // Validations
            // share path
            if ((sharePath == null) || (sharePath == string.Empty))
            {
                throw new ArgumentException("Share path cannot be empty");
            }
            else if (Directory.Exists(sharePath) == false)
            {
                throw new ArgumentException("Share path has to be a valid folder");
            }

            // share name
            if ((shareName == null) || (shareName == string.Empty))
            {
                throw new ArgumentException("Share name cannot be empty");
            }
            else if (IsShareNameAvailable(shareName) == false)
            {
                throw new ArgumentException("Share name is not available");
            }

            // users list
            if (users == null)
            {
                throw new ArgumentException("User list cannot be null");
            }

            List<string> warningsList = new List<string>();

            // Creating the WMI class for Win32 share
            ManagementClass shareManager = new ManagementClass("Win32_Share");

            // Getting setter for input parameters for Create method
            ManagementBaseObject inputParams = shareManager.GetMethodParameters("Create");

            // Creating security descriptor for the share
            ManagementClass securityDescriptorDefault = new ManagementClass("Win32_SecurityDescriptor");
            securityDescriptorDefault.Properties["ControlFlags"].Value = 0x8; // Indicates an SD with a default DACL. the object receives the default DACL from the access token of the creator 

            // setting the input parameters
            inputParams["Path"] = sharePath;
            inputParams["Name"] = shareName;
            inputParams["Description"] = shareDescription;
            // inParams["MaximumAllowed"] = 10; // not used currently
            inputParams["Type"] = 0x0;  // 0 indicates Disk Drive other types and values are given below
                                        // DISK_DRIVE = 0x0
                                        // PRINT_QUEUE = 0x1
                                        // DEVICE = 0x2
                                        // IPC = 0x3
                                        // DISK_DRIVE_ADMIN = 0x80000000
                                        // PRINT_QUEUE_ADMIN = 0x80000001
                                        // DEVICE_ADMIN = 0x80000002
                                        // IPC_ADMIN = 0x8000003
            inputParams["Access"] = securityDescriptorDefault;

            // executing the create method and getting the result
            ManagementBaseObject outParams = shareManager.InvokeMethod("Create", inputParams, null);            

            if ((outParams != null) && ((uint)(outParams.Properties["ReturnValue"].Value) == 0))
            {
                // setting the share unce path
                shareUNCPath = String.Format(@"\\{0}\{1}", System.Environment.MachineName, shareName);
                
                if ((users != null) && (users.Length > 0))
                {
                    //
                    // share create succeeded -- assign permissions to given set of users now
                    //
                    string[] permWarnings = null;
                    bool bRet = AssignPermissionsToUsers(shareName, users, ref permWarnings);
                    if (permWarnings != null)
                    {
                        warningsList.AddRange(permWarnings);
                    }

                    warnings = warningsList.ToArray();
                    return bRet;
                }
                else
                {
                    string msg = "No valid usernames given. Default permissions set.";
                    warningsList.Add(msg);
                     LoggerHelper.LogCCF20(msg);

                    warnings = warningsList.ToArray();
                    return true; // share created with default access
                }
            }
            else
            {
                return false; // creation failed -- method returned failure
            }           
        }

        /// <summary>
        /// Assign permissions to given set of users in the specified share
        /// </summary>
        /// <param name="shareName">Name of the share to be changed</param>
        /// <param name="users">set of users</param>
        /// <param name="warnings"></param>
        /// <returns></returns>
        public static bool AssignPermissionsToUsers(string shareName, User[] users, ref string[] warnings)
        {
            List<string> warningsList = new List<string>();
            
            // Step 1: Get the security descriptor of the shared folder object
            ManagementObject sharedFolder = GetSharedFolderSecuritySettingObject(shareName);
            if (sharedFolder == null)
            {
                 LoggerHelper.LogCCF20("The shared folder with given name does not exist");
                return false;
            }
            ManagementBaseObject securityDescriptorObject = sharedFolder.InvokeMethod("GetSecurityDescriptor", null, null);
            if (securityDescriptorObject == null)
            {
                 LoggerHelper.LogCCF20(string.Format(CultureInfo.InvariantCulture, "Error extracting security descriptor of the shared path {0}.", shareName));
                return false;
            }
            int returnCode = Convert.ToInt32(securityDescriptorObject.Properties["ReturnValue"].Value);
            if (returnCode != 0)
            {
                 LoggerHelper.LogCCF20(string.Format(CultureInfo.InvariantCulture, "Error extracting security descriptor of the shared path {0}. Error Code{1}.", shareName, returnCode.ToString()));
                return false;
            }

            ManagementBaseObject securityDescriptor = (ManagementBaseObject)securityDescriptorObject.Properties["Descriptor"].Value;

            // Step 2: Create an access control list to be assigned
            List<ManagementObject> accessControlList = new List<ManagementObject>();

            // Step 3: Add all the users to the access control list
            for (int i = 0; i < users.Length; i++)
            {
                string domainName = string.Empty;
                string userName = string.Empty;

                SplitUserName(users[i].UserName, ref domainName, ref userName);
                LoggerHelper.LogCCF20(string.Format("[ShareFolder] Domain={0} User={1}", domainName,userName));
                // Getting the user account object
                ManagementObject userAccountObject = GetUserAccountObject(domainName, userName);
                if (userAccountObject != null)
                {
                    ManagementObject securityIdentfierObject = new ManagementObject("Win32_SID.SID='S-1-1-0'");
                    LoggerHelper.LogCCF20(string.Format("[ShareFolder] User SID={0}",(string)userAccountObject.Properties["SID"].Value));
                    securityIdentfierObject.Get();

                    if (securityIdentfierObject != null)
                    {
                        LoggerHelper.LogCCF20("[ShareFolder] Find Everyone SID");
                    }
                    else
                    {
                        LoggerHelper.LogCCF20("[ShareFolder] Can't find Everyone SID");
                    }
                    // Creating Trustee Object
                    ManagementObject trusteeObject = CreateTrustee(domainName, userName, securityIdentfierObject);

                    // Creating Access Control Entry
                    ManagementObject accessControlEntry = CreateAccessControlEntry(trusteeObject, false, users[i].AllowRead, users[i].AllowFullControl);

                    // Adding entry to access control list
                    accessControlList.Add(accessControlEntry);
                }
                else
                {
                    string msg = "The user with Domain-'" + domainName + "' and Name-'" + userName
                        + "' could not be found. No permissions set for the user";
                    warningsList.Add(msg);
                     LoggerHelper.LogCCF20(msg);
                }
            }

            if (accessControlList.Count > 0)
            {
                // Step 4: Assign access Control list to security desciptor
                securityDescriptor.Properties["DACL"].Value = accessControlList.ToArray();

                // Step 5: Setting the security descriptor for the shared folder
                ManagementBaseObject parameterForSetSecurityDescriptor = sharedFolder.GetMethodParameters("SetSecurityDescriptor");
                parameterForSetSecurityDescriptor["Descriptor"] = securityDescriptor;
                sharedFolder.InvokeMethod("SetSecurityDescriptor", parameterForSetSecurityDescriptor, null);
            }
            else
            {
                string msg = "No valid usernames given. Default permissions set.";
                warningsList.Add(msg);
                 LoggerHelper.LogCCF20(msg);
            }

            warnings = warningsList.ToArray();
            if (warningsList.Count > 0)
            {
                return false;
            }
            else
            {

                return true;
            }
        }

        /// <summary>
        /// Deletes the share with given name
        /// </summary>
        /// <param name="shareName"></param>
        /// <returns></returns>
        public static bool RemoveShare(string shareName)
        {
            if ((shareName == null) || (shareName == string.Empty))
            {
                throw new ArgumentException("shareName cannot be empty");
            }

            // Getting the share object and calling delete function on it
            ManagementObject sharedFolder = GetSharedFolderObject(shareName);
            if (sharedFolder != null)
            {
                ManagementBaseObject outParams = sharedFolder.InvokeMethod("Delete", null, null);
                if ((outParams != null) && ((uint)(outParams.Properties["ReturnValue"].Value) == 0))
                {
                    LoggerHelper.LogCCF20(string.Format("[ShareFolder] delete share name {0}",shareName));
                    return true;
                }
            }
            else
            {
                LoggerHelper.LogCCF20("Share with name '" + shareName + "' does not exist. Count not delete share");
            }

            return false;
        }

        /// <summary>
        /// This method checks if the given share name is available
        /// </summary>
        /// <param name="sharedFolderName">share name</param>
        /// <returns>true if the share name is available, false otherwise</returns>
        public static bool IsShareNameAvailable(string shareName)
        {
            bool isAvailable = true;

            //Creating a searcher object to search
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from Win32_Share where Name = '" + shareName + "'");
            ManagementObjectCollection resultOfSearch = searcher.Get();
            if (resultOfSearch.Count > 0)
            {
                isAvailable = false;
            }

            return isAvailable;
        }

        /// <summary>
        /// This method checks if the given share name is available
        /// </summary>
        /// <param name="sharedFolderName">share name</param>
        /// <returns>true if the share name is available, false otherwise</returns>
        public static bool IsShareNameAvailableAtPath(string shareName, string path)
        {
            bool isAvailable = false;

            //Creating a searcher object to search
            path = path.Replace(@"\", @"\\"); // this is required coz of conflict in handling of \ by WMI and C#
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from Win32_Share where Name = '" + shareName + "' and Path = '" + path + "'");
            ManagementObjectCollection resultOfSearch = searcher.Get();
            if (resultOfSearch.Count > 0)
            {
                isAvailable = true;
            }

            return isAvailable;
        }

        /// <summary>
        /// Returns names of all the shares at a particular location in disk
        /// and also the assciated users
        /// </summary>
        /// <param name="path">full path of the location</param>
        /// <returns>Array of all the share names available</returns>
        public static Share[] GetAllShareNames(string path)
        {
            List<Share> sharesList = new List<Share>();

            //Creating a searcher object to search
            path = path.Replace(@"\", @"\\"); // this is required coz of conflict in handling of \ by WMI and C#
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from Win32_Share where Path = '" + path + "'");
            ManagementObjectCollection resultOfSearch = searcher.Get();
            if (resultOfSearch.Count > 0)
            {
                foreach (ManagementObject share in resultOfSearch)
                {
                    Share shareDetails = new Share();                    

                    string shareName = share.Properties["Name"].Value.ToString();
                    shareDetails.ShareName = shareName;
                    shareDetails.UsersList = new List<User>();
                    sharesList.Add(shareDetails);

                    // retrieving the DACL of the share
                    // Step 1: Get the security descriptor of the shared folder object
                    ManagementObject sharedFolder = GetSharedFolderSecuritySettingObject(shareName);
                    if (sharedFolder == null)
                    {
                         LoggerHelper.LogCCF20("The shared folder with name " + shareName + " does not exist");
                        continue;
                    }
                    ManagementBaseObject securityDescriptorObject = sharedFolder.InvokeMethod("GetSecurityDescriptor", null, null);
                    if (securityDescriptorObject == null)
                    {
                         LoggerHelper.LogCCF20(string.Format(CultureInfo.InvariantCulture, "Error extracting security descriptor of the shared path {0}.", shareName));
                        continue;
                    }
                    int returnCode = Convert.ToInt32(securityDescriptorObject.Properties["ReturnValue"].Value);
                    if (returnCode != 0)
                    {
                         LoggerHelper.LogCCF20(string.Format(CultureInfo.InvariantCulture, "Error extracting security descriptor of the shared path {0}. Error Code{1}.", shareName, returnCode.ToString()));
                        continue;
                    }

                    ManagementBaseObject securityDescriptor = (ManagementBaseObject)securityDescriptorObject.Properties["Descriptor"].Value;
                    ManagementBaseObject[] accessControlList = (ManagementBaseObject[])securityDescriptor.Properties["DACL"].Value;
                    foreach (ManagementBaseObject aceObject in accessControlList)
                    {
                        uint accessMask = (uint)aceObject.Properties["AccessMask"].Value;
                        uint aceType = (uint)aceObject.Properties["AceType"].Value; // 0 = allow, 1 = deny
                        ManagementBaseObject trustee = (ManagementBaseObject)aceObject.Properties["Trustee"].Value;
                        if (aceType == 0) // allow access
                        {
                            User user = new User();
                            user.UserName = (string)trustee.Properties["Domain"].Value + "\\" + (string)trustee.Properties["Name"].Value;
                            user.AllowRead = ((accessMask & NTFS_READ) == NTFS_READ) ? true : false;
                            user.AllowFullControl = ((accessMask & NTFS_FULLCONTROL) == NTFS_FULLCONTROL) ? true : false;

                            shareDetails.UsersList.Add(user);
                        }
                    }
                }
            }

            return sharesList.ToArray();
        }

        /// <summary>
        /// Checks whether given user names are available
        /// If not they are copied to InvalidNames array
        /// If yes adds the full name of user to userMap
        /// </summary>
        /// <param name="userNames"></param>
        /// <param name="invalidNames"></param>
        /// <returns></returns>
        public static bool GetInvalidUsers(string[] userNames, ref string[] invalidNames, ref Dictionary<string, string> userMap)
        {
            try
            {
                List<string> invalidNamesList = new List<string>();
                userMap = new Dictionary<string, string>();

                for (int i = 0; i < userNames.Length; i++)
                {
                    string domainName = string.Empty;
                    string userName = string.Empty;

                    SplitUserName(userNames[i], ref domainName, ref userName);

                    ManagementObjectSearcher searcher = new ManagementObjectSearcher(string.Format("select * from Win32_Account where Name = '{0}' and Domain='{1}'", userName, domainName));
                    ManagementObjectCollection resultOfSearch = searcher.Get();
                    if (resultOfSearch.Count > 0)
                    {
                        foreach (ManagementObject userObject in resultOfSearch)
                        {
                            // Adding full name to the userMap
                            userMap.Add(userNames[i], userObject.Properties["FullName"].Value.ToString());
                        }
                    }
                    else
                    {
                        invalidNamesList.Add(userNames[i]);
                    }
                }

                invalidNames = invalidNamesList.ToArray();
                return true;
            }
            catch (ManagementException ex)
            {
                 LoggerHelper.LogCCF20("Error occurred while searching for names : " + ex.ToString());
                return false;
            }
        }
    }
}
